package com.spring.nio.business.file.service.impl;

import com.google.common.collect.Lists;
import com.spring.nio.business.file.mapper.FileRepository;
import com.spring.nio.business.file.repository.dataobject.FileDo;
import com.spring.nio.business.file.repository.param.FileDalQueryParam;
import com.spring.nio.business.file.service.FileService;
import com.spring.nio.business.file.service.dto.FileDto;
import com.spring.nio.business.file.service.param.FileCreateParam;
import com.spring.nio.business.file.service.param.FileQueryParam;
import com.spring.nio.business.file.service.param.FileUpdateParam;
import com.spring.nio.business.user.dto.UserDTO;
import com.spring.nio.business.user.param.UserQueryParam;
import com.spring.nio.business.user.service.UserService;
import com.spring.nio.common.BasicConstant;
import com.spring.nio.common.YesOrNoEnum;
import com.spring.nio.core.configuration.NioServerBootstrap;
import com.spring.nio.core.repository.result.PageResult;
import com.spring.nio.core.server.FileSystemServer;
import com.spring.nio.service.file.model.FileEndTransportRequest;
import com.spring.nio.service.file.model.structure.FileTransportTaskDTO;
import com.spring.nio.util.LocalTime;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.io.File;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * 文件服务
 * @author spring
 * */
@Slf4j
@Service
public class FileServiceImpl implements FileService {

    @Autowired
    private FileRepository fileRepository;

    /**
     * 获取所有文件夹
     * @return List<FileDto>
     * */
    @Override
    public List<FileDto> getAllFileFolder() {
        FileDalQueryParam fileDalQueryParam = new FileDalQueryParam();
        fileDalQueryParam.setIsFile(YesOrNoEnum.N.name());
        fileDalQueryParam.setIsExist(YesOrNoEnum.Y.name());
        List<FileDto> fileDtoList = Lists.newArrayList();
        List<FileDo> fileDos = this.fileRepository.query(fileDalQueryParam);
        fileDos.stream().forEach(fileDo -> fileDtoList.add(this.doToDto(fileDo)));
        return fileDtoList;
    }

    /**
     * 根据当前点击的文件夹名称查询当前文件夹信息
     * @param fileQueryParam
     * @return List<FileDo>
     */
    private List<FileDo> getCurrentFileInfo(FileQueryParam fileQueryParam) {
        return this.fileRepository.getCurrentFileInfo(this.createDalParam(fileQueryParam));
    }

    /**
     * 查询当前文件夹路径以及第一子路径数据(包含文件夹或文件)，文件夹显示在文件树中，文件显示在列表中
     * @param fileQueryParam   文件查询param 
     * @param completeFilePath 文件绝对路径
     * @param relativeFilePath 文件相对路径
     * @return
     */
    @Override
    public FileDto getFolderFileTree(FileQueryParam fileQueryParam, String completeFilePath, String relativeFilePath) {
        // 1、查询当前fileQueryParam指定的文件信息
        List<FileDo> fileDos = this.getCurrentFileInfo(fileQueryParam);
        if(CollectionUtils.isEmpty(fileDos)) {
            // DB未查询到当前文件夹信息，则验证文件系统是否存在文件夹目录，存在则创建文件夹目录DB记录
            if(!this.verifyFileSystemExist(fileQueryParam, completeFilePath)) {
                return new FileDto();
            }

            // 执行创建(创建文件目录，创建DB记录)
            return this.createFile(fileQueryParam, relativeFilePath);
        }

        // 2、获取当前查询出的文件夹信息
        FileDto currentFileDto = this.doToDto(fileDos.get(0));
        currentFileDto.setChildFileList(Lists.newArrayList());
        FileQueryParam childFileQueryParam = new FileQueryParam();
        childFileQueryParam.setPId(currentFileDto.getId());
        childFileQueryParam.setIsExist(YesOrNoEnum.Y.name());
        List<FileDo> childFileDos = this.getCurrentFileInfo(childFileQueryParam);
        childFileDos.stream().forEach(fileDo -> { currentFileDto.getChildFileList().add(this.doToDto(fileDo));});
        return currentFileDto;
    }

    /**
     * 校验文件系统中文件是否存在
     * @param fileQueryParam
     * @param completeFilePath
     * @return Boolean
     * */
    private Boolean verifyFileSystemExist(FileQueryParam fileQueryParam, String completeFilePath) {
        File newFile = new File(completeFilePath);
        if(!newFile.exists()) {
            // 当前文件夹在文件系统和数据库都不存在，如果当前文件是文件夹则创建文件系统目录
            newFile.mkdirs();
        }

        // 重置文件夹部分信息
        fileQueryParam.setFileType(newFile.isDirectory()?"NOT_FILE":"FILE");
        fileQueryParam.setIsExist(YesOrNoEnum.Y.name());
        fileQueryParam.setHasChild(YesOrNoEnum.N.name());
        return Boolean.TRUE;
    }

    /**
     * 创建文件
     * @param fileQueryParam
     * @param relativeFilePath 待创建文件相对路径
     * @return fileDto
     * */
    private FileDto createFile(FileQueryParam fileQueryParam, String relativeFilePath) {
        FileCreateParam fileCreateParam = new FileCreateParam();
        fileCreateParam.setPId(fileQueryParam.getPId());
        fileCreateParam.setFileName(fileQueryParam.getFileName());
        fileCreateParam.setUserId(fileQueryParam.getUserId());
        fileCreateParam.setFilePath(relativeFilePath);
        fileCreateParam.setFileSize(fileQueryParam.getFileSize());
        fileCreateParam.setFileType(fileQueryParam.getFileType());
        fileCreateParam.setIsFile(fileQueryParam.getIsFile());
        fileCreateParam.setIsExist(fileQueryParam.getIsExist());
        fileCreateParam.setHasChild(fileQueryParam.getHasChild());
        FileDo fileDo = this.createParamToDo(fileCreateParam);
        this.fileRepository.insertSelective(fileDo);

        log.info("[ " + LocalTime.formatDate(LocalDateTime.now()) + " ] FileServiceImpl | --> 创建文件[{}]DB记录成功, 文件存储路径 [{}], thread = {}",
            fileDo.getFileName(), fileDo.getFilePath(), Thread.currentThread().getName());
        return this.doToDto(fileDo);
    }

    /**
     * 关闭用户上传或下载文件任务
     * @param fileEndTransportRequest
     * */
    @Override
    public void finishFileTransportTask(FileEndTransportRequest fileEndTransportRequest) {
        // 1、插入文件成功传送DB记录数据
        FileQueryParam fileQueryParam = new FileQueryParam();
        fileQueryParam.setPId(Long.valueOf(fileEndTransportRequest.getPId()));
        fileQueryParam.setUserId(Long.valueOf(fileEndTransportRequest.getUserId()));
        fileQueryParam.setFileName(fileEndTransportRequest.getFileName());
        fileQueryParam.setFileSize(Long.valueOf(fileEndTransportRequest.getFileSize()));
        fileQueryParam.setFilePath(fileEndTransportRequest.getFilePath() + fileQueryParam.getFileName());
        fileQueryParam.setFileType("FILE");
        this.createFile(fileQueryParam);

        // 2、关闭当前用户的文件传送缓存任务
        UserQueryParam userQueryParam = new UserQueryParam();
        userQueryParam.setUserName(fileEndTransportRequest.getUserName());
        UserDTO userDto = NioServerBootstrap.getConfig().getClassPathXmlApplicationContext().getBean(UserService.class).getOnlineUserByName(userQueryParam);
        userDto.setFileSocketChannelContext(null);
        if(!CollectionUtils.isEmpty(userDto.getUploadFileMap())) {
            FileTransportTaskDTO fileTransportTaskDto = userDto.getUploadFileMap().remove(fileEndTransportRequest.getFileTransportAddress());
            if(!Objects.isNull(fileTransportTaskDto)) {
                // 上传成功，释放待上传文件占用资源,即删除userDto中map对应的key-value
                try {
                    // 设置任务状态为完成
                    fileTransportTaskDto.setTaskStatus("FINISH");
                    FileSystemServer.closeFileChannel(fileTransportTaskDto.getFileChannel());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        log.info("[ " + LocalTime.formatDate(LocalDateTime.now()) + " ] FileServiceImpl | --> 当前用户 [{}] 已完成在线文件传输或上传, 已关闭相应文件通道以及socketChannel", fileEndTransportRequest.getUserName());
    }

    /**
     * 查询文件信息
     * @param fileQueryParam
     * */
    @Override
    public List<FileDto> queryFileList(FileQueryParam fileQueryParam) {
        final List<FileDto> fileDtos = Lists.newArrayList();
        this.fileRepository.queryFileList(fileQueryParam).stream().forEach(fileDo -> { fileDtos.add(this.doToDto(fileDo)); });
        return fileDtos;
    }

    /**
     * 查询文件总记录数
     * @param fileQueryParam
     * */
    @Override
    public Integer queryFileListSize(FileQueryParam fileQueryParam) {
        return this.fileRepository.queryFileListSize(fileQueryParam);
    }

    /**
     * 查询当前文件夹下的文件
     * @param id  当前文件夹Id
     * @param currentPage 当前页
     * @param pageSize 数量
     * @return
     */
    private PageResult<FileDo> queryFilesBelongCurrentFolder(Long id, Integer currentPage, Integer pageSize) {
        FileDalQueryParam fileDalQueryParam = new FileDalQueryParam();
        fileDalQueryParam.setPId(id);
        fileDalQueryParam.setIsExist(YesOrNoEnum.Y.name());
        fileDalQueryParam.setIsFile(YesOrNoEnum.Y.name());
        fileDalQueryParam.setDel(YesOrNoEnum.N.name());
        fileDalQueryParam.setCurrentPage(currentPage);
        fileDalQueryParam.setPageSize(pageSize);

        return this.fileRepository.queryPage(fileDalQueryParam);
    }

    @Override
    public FileDto create(FileQueryParam fileQueryParam, String completeFilePath) {
        // 1、判断当前新创建的文件夹路径是否存在记录，存在则无需创建，直接返回当前存在的文件
        List<FileDo> fileDos = this.getCurrentFileInfo(fileQueryParam);
        if(!CollectionUtils.isEmpty(fileDos)) {
            FileDto fileDto = this.doToDto(fileDos.get(0));
            fileDto.setRepeatCreate(YesOrNoEnum.Y.name());
            return fileDto;
        }

        // 2、判断当前待创建新文件夹所选中的文件夹下是否含有文件，含有则不能在当前文件夹路径下创建子文件夹
        FileQueryParam newFileQueryParam = new FileQueryParam();
        newFileQueryParam.setPId(fileQueryParam.getPId());
        newFileQueryParam.setFileType("FILE");
        newFileQueryParam.setIsFile(YesOrNoEnum.Y.name());
        newFileQueryParam.setIsExist(YesOrNoEnum.Y.name());
        newFileQueryParam.setCurrentPage(1);
        newFileQueryParam.setPageSize(2);
        PageResult<FileDo> pageResult = this.fileRepository.queryPage(this.createDalParam(newFileQueryParam));
        if(null != pageResult && !CollectionUtils.isEmpty(pageResult.getModelList())) {
            FileDto fileDto = new FileDto();
            fileDto.setId(fileQueryParam.getPId());
            fileDto.setFileName(fileQueryParam.getFilePath());
            fileDto.setFileCount(pageResult.getTotalCount());
            return fileDto;
        }

        // 3、为空，文件系统创建当前文件夹
        FileSystemServer.createFolder(completeFilePath);

        // 创建新的文件夹节点持久到数据库
        FileCreateParam fileCreateParam = new FileCreateParam();
        fileCreateParam.setPId(fileQueryParam.getPId());
        fileCreateParam.setUserId(fileQueryParam.getUserId());
        fileCreateParam.setFileName(fileQueryParam.getFileName());
        fileCreateParam.setFilePath(fileQueryParam.getFilePath());
        fileCreateParam.setFileType(fileQueryParam.getFileType());
        fileCreateParam.setFileSize(fileQueryParam.getFileSize());
        fileCreateParam.setIsFile(fileQueryParam.getIsFile());
        fileCreateParam.setIsExist(YesOrNoEnum.Y.name());
        fileCreateParam.setHasChild(YesOrNoEnum.N.name());
        FileDo fileDo = this.createParamToDo(fileCreateParam);
        this.fileRepository.insertSelective(fileDo);

        // 更新父节点的hasChild状态为true
        fileDo = new FileDo();
        fileDo.setId(fileQueryParam.getPId());
        fileDo.setHasChild(YesOrNoEnum.Y.name());
        this.fileRepository.updateSelective(fileDo);
        FileDto fileDto = this.doToDto(fileDo);
        fileDto.setRepeatCreate(YesOrNoEnum.N.name());
        return fileDto;
    }

    @Override
    public FileDto update(FileUpdateParam fileUpdateParam, String originFilePath, String completeFilePath) {
        File file = new File(originFilePath);
        if(file.exists() && file.isDirectory()) {
            // 创建多级文件夹
            file.renameTo(new File(completeFilePath));
        }

        FileDo fileDo = this.updateParamToDo(fileUpdateParam);
        this.fileRepository.updateSelective(fileDo);
        return this.doToDto(fileDo);
    }

    @Override
    public FileDto createFile(FileQueryParam fileQueryParam) {
        List<FileDo> fileDos = this.getCurrentFileInfo(fileQueryParam);
        if(CollectionUtils.isEmpty(fileDos)) {
            FileCreateParam fileCreateParam = new FileCreateParam();
            fileCreateParam.setPId(fileQueryParam.getPId());
            fileCreateParam.setUserId(fileQueryParam.getUserId());
            fileCreateParam.setFileName(fileQueryParam.getFileName());
            fileCreateParam.setFileSize(fileQueryParam.getFileSize());
            fileCreateParam.setFilePath(fileQueryParam.getFilePath());
            fileCreateParam.setFileType(fileQueryParam.getFileType());
            fileCreateParam.setIsFile(YesOrNoEnum.Y.name());
            fileCreateParam.setIsExist(YesOrNoEnum.Y.name());
            fileCreateParam.setHasChild(YesOrNoEnum.N.name());
            FileDo fileDo = this.createParamToDo(fileCreateParam);
            this.fileRepository.insertSelective(fileDo);
            log.info("[ " + LocalTime.formatDate(LocalDateTime.now()) + " ] FileServiceImpl | --> 成功持久化上传文件 [{}], 文件存储路径 [{}], thread = {}",
               fileDo.getFileName(), fileDo.getFilePath(), Thread.currentThread().getName());
            return this.doToDto(fileDo);
        }

        return new FileDto();
    }

    @Override
    public Boolean deleteFile(FileQueryParam fileQueryParam, List<Long> fileIdList) {
        if(CollectionUtils.isEmpty(fileIdList)) {
            return Boolean.FALSE;
        }

        final Date date = new Date();
        List<FileDo> fileDoList = Lists.newArrayList();
        fileIdList.stream().forEach(fileId -> {
            FileUpdateParam fileUpdateParam = new FileUpdateParam();
            fileUpdateParam.setId(fileId);
            fileUpdateParam.setGmtModified(date);
            fileUpdateParam.setDel(YesOrNoEnum.Y.name());
            fileUpdateParam.setDelTime(date);

            //fileDoList.add(this.updateParamToDo(fileUpdateParam));
            this.fileRepository.updateSelective(this.updateParamToDo(fileUpdateParam));
        });

        //this.fileRepository.batchUpdateSelective(fileDoList);
        return Boolean.TRUE;
    }

    @Override
    public FileDto getFileById(FileQueryParam fileQueryParam) {
        return this.doToDto(this.fileRepository.get(fileQueryParam.getId()));
    }

    private FileDo createParamToDo(FileCreateParam param) {
        FileDo fileDo = new FileDo();
        fileDo.setPId(param.getPId());
        fileDo.setUserId(param.getUserId());
        fileDo.setFileName(param.getFileName());
        fileDo.setFilePath(param.getFilePath());
        fileDo.setFileSize(param.getFileSize());
        fileDo.setFileType(param.getFileType());
        fileDo.setIsFile(param.getIsFile());
        fileDo.setIsExist(param.getIsExist());
        fileDo.setHasChild(param.getHasChild());
        fileDo.setDel(YesOrNoEnum.N.name());
        Date date = new Date();
        fileDo.setGmtCreate(date);
        fileDo.setGmtModified(date);
        fileDo.setDelTime(null);
        return fileDo;
    }

    private FileDo updateParamToDo(FileUpdateParam param) {
        FileDo fileDo = new FileDo();
        fileDo.setId(param.getId());
        fileDo.setPId(param.getPId());
        fileDo.setUserId(param.getUserId());
        fileDo.setFileName(param.getFileName());
        fileDo.setFilePath(param.getFilePath());
        fileDo.setFileSize(param.getFileSize());
        fileDo.setFileType(param.getFileType());
        fileDo.setIsFile(param.getIsFile());
        fileDo.setIsExist(param.getIsExist());
        fileDo.setHasChild(param.getHasChild());
        fileDo.setDel(param.getDel());
        fileDo.setGmtModified(param.getGmtModified());
        fileDo.setDelTime(param.getDelTime());
        return fileDo;
    }

    private FileDalQueryParam createDalParam(FileQueryParam fileQueryParam) {
        FileDalQueryParam fileDalQueryParam = new FileDalQueryParam();
        fileDalQueryParam.setPId(fileQueryParam.getPId());
        fileDalQueryParam.setUserId(fileQueryParam.getUserId());
        fileDalQueryParam.setFileName(fileQueryParam.getFileName());
        fileDalQueryParam.setFilePath(fileQueryParam.getFilePath());
        fileDalQueryParam.setFileSize(fileQueryParam.getFileSize());
        fileDalQueryParam.setFileType(fileQueryParam.getFileType());
        fileDalQueryParam.setIsFile(fileQueryParam.getIsFile());
        fileDalQueryParam.setIsExist(fileQueryParam.getIsExist());
        fileDalQueryParam.setHasChild(fileQueryParam.getHasChild());
        fileDalQueryParam.setCurrentPage(fileQueryParam.getCurrentPage());
        fileDalQueryParam.setPageSize(fileQueryParam.getPageSize());
        fileDalQueryParam.setDel(YesOrNoEnum.N.name());
        fileDalQueryParam.setOrderBy(fileQueryParam.getOrderBy());
        return fileDalQueryParam;
    }

    private FileDto doToDto(FileDo fileDo) {
        FileDto fileDto = new FileDto();
        fileDto.setId(fileDo.getId());
        fileDto.setPId(fileDo.getPId());
        fileDto.setUserId(fileDo.getUserId());
        fileDto.setFileName(fileDo.getFileName());
        fileDto.setFilePath(fileDo.getFilePath());
        fileDto.setFileSize(fileDo.getFileSize());
        fileDto.setFileType(fileDo.getFileType());
        fileDto.setIsFile(fileDo.getIsFile());
        fileDto.setIsExist(fileDo.getIsExist());
        fileDto.setHasChild(fileDo.getHasChild());
        fileDto.setDel(fileDo.getDel());
        fileDto.setDelTime(fileDo.getDelTime());
        fileDto.setGmtCreate(fileDo.getGmtCreate());
        fileDto.setGmtModified(fileDo.getGmtModified());
        return fileDto;
    }
}
